/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.ponly.uno4j.core;

import com.sun.star.bridge.XBridge;
import com.sun.star.bridge.XBridgeFactory;
import com.sun.star.comp.helper.Bootstrap;
import com.sun.star.connection.NoConnectException;
import com.sun.star.connection.XConnection;
import com.sun.star.connection.XConnector;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.lang.EventObject;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XEventListener;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lib.uno.helper.UnoUrl;
import com.sun.star.uno.XComponentContext;
import org.ponly.common.util.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.ponly.uno4j.util.UnoUtils.*;

/**
 * UNO Bridge (UniversalNetwork Object Bridge)
 * <p/>
 * UNO-Url语法: [uno:]connection-type,parameters;protocol-name,parameters;objectname<br>
 * eg:<br>
 * 1. uno:socket,host=localhost,port=2002;urp;StarOffice.ServiceManager<br>
 * 2. uno:pipe,name=mypipename;urp;MyObjectName<br>
 * <p/>
 * UNO 支持两种连接方式: pipe, socket<br>
 * 1. Pipe 方式: Server、Client 处于同一个台机器, 共享内存, 相对于 Socket 效率更高
 * 2. Socket 方式: Server、Client 不一定要在同一台机器, 通过 socket 进行通讯
 * <p/>
 * 警告:
 * 1. 每个 UNO Bridge 是线程安全的<br>
 * 2. 对于同一个 pipe, socket 服务进程只能进行串行处理(不能同时建立多个 Bridge或同一Bridge同时处理多个任务, 同一 pipe/socket 进程只能建立一个bridge处理)<br>
 * 3. 对于同一工作空间只能串行处理(默认情况下,服务进程会在同一工作空间进行处理, 可以通过 -env:UserInstallation=file:///path/指定不同工作空间)<br>
 * 4. PIPE 方式通过 name 来识别不同的 PIPE Bridge, 因此不同的 pipe bridge 应该有不同的名称
 * 因此，每个 soffice 进程应该有唯一的工作空间且同时只能建立一个bridge进行处理
 * <p/>
 * 附所需 jar:
 * 第一种方式: maven repositories
 * 第二种方式:
 * 下载 <a href="http://zh-cn.libreoffice.org/download/libreoffice-still/">SDK</a>
 * 添加 jar:
 * 1. /usr/share/libreoffice/ure-link/share/java/juh.jar,
 * 2. /usr/share/libreoffice/ure-link/share/java/jurt.jar,
 * 3. /usr/share/libreoffice/ure-link/share/java/ridl.jar
 * 4. /usr/share/libreoffice/program/classes/unoil.jar
 * 对于 4, 需要安装 SDK: Ubuntu libreoffice SDK sudo apt-get install libreoffice-dev.
 * MAC OS X 下 下载 SDK 运行 setsdkenvunix over, 到 Libreoffice.app/Content 下查找以上目录
 * <p/>
 * 参考资料《RedOffice 高级开发支持》
 *
 * @author vacoor
 * @see UnoBridge
 * @see <a href="http://www.openoffice.org/udk/common/man/spec/uno-url.html">UNO-Url</a>
 */
@SuppressWarnings({"unused"})
public class UnoBridge {
    private static final Logger LOG = LoggerFactory.getLogger(UnoBridge.class);

    private static final String UNO_URL_RESOLVER = "com.sun.star.bridge.UnoUrlResolver";
    private static final String BRIDGE_FACTORY = "com.sun.star.bridge.BridgeFactory";
    private static final String CONNECTOR = "com.sun.star.connection.Connector";
    private static final String BRIDGE_COMP_CONTEXT = "StarOffice.ComponentContext";
    private static final String BRIDGE_SMGR_NAME = "StarOffice.ServiceManager";
    private static final String BRIDGE_DESKTOP = "com.sun.star.frame.Desktop";

    private static final String BRIDGE_PREFIX = "Uno-Bridge-";      // 桥名称前缀
    private static final Object BRIDGE_MONITOR = new Object();      // 桥连接锁

    private static final int RETRY_COUNT = 3;

    //    private final String url;
//    private final String bridgeType;
    private final UnoUrl unoUrl;
    private final XEventListener xBridgeMonitor = new XEventListener() {
        @Override
        public void disposing(EventObject paramEventObject) {
            LOG.warn("接收到服务器 disposing 事件(将要断开连接), {}", paramEventObject);
            disconnect(); // bridge 异常/手动断开时调用断开方法, 这样写好像不太好
        }
    };
    private transient XBridge xBridge;


    public UnoBridge(String unoUrl) {
        this(parseUnoUrl(unoUrl));
    }

    public UnoBridge(UnoUrl unoUrl) {
        String connType = unoUrl.getConnection();
        String conn = unoUrl.getConnectionAndParametersAsString();

        if ("socket".equals(connType) && !conn.toLowerCase().contains("tcpnodelay")) {
            // 对于 Socket 默认会有 200ms 延迟
            String url = unoUrl.getConnectionAndParametersAsString() + ",tcpNoDelay=1" + ";" + unoUrl.getProtocolAndParametersAsString() + unoUrl.getRootOid();
            unoUrl = parseUnoUrl(url);
        }

        this.unoUrl = unoUrl;
    }

    /**
     * 连接当前 UNO Bridge
     *
     * @throws Exception
     */
    public UnoBridge connect() throws Exception {
        if (isConnected()) {
            return this;
        }

        if ("pipe".equals(unoUrl.getConnection().toLowerCase())) {
            loadJPipeLibraryIfNecessary(null);
        }

        final XComponentContext xLocaleComponentContext = Bootstrap.createInitialComponentContext(null);    // 创建组件上下文
        final XMultiComponentFactory serviceManager = xLocaleComponentContext.getServiceManager();

        // 这里不同工厂实例其实 bridge 是共存的, 所以就不处理了
        final XBridgeFactory xBridgeFactory = $c(xLocaleComponentContext, XBridgeFactory.class, BRIDGE_FACTORY);

        if (null == serviceManager) {
            throw new IllegalStateException("no initial service manager!");
        }

        /*-
         * 桥名称, 不应该存在多个 bridge
         */
        final String xBridgeName = BRIDGE_PREFIX + unoUrl.getConnectionAndParametersAsString();
        final String xBridgeUrl = unoUrl.getConnectionAndParametersAsString();
        final String xBridgeType = unoUrl.getProtocol();
        synchronized (BRIDGE_MONITOR) {
            xBridge = xBridgeFactory.getBridge(xBridgeName);    // 获取已经存在的 xBridge

            if (null != xBridge) {
                // throw new IllegalStateException("当前服务已经存在一个相同的Bridge: " + xBridgeName);
                LOG.warn("同一服务不应该有多个 Bridge, 使用已经存在的 UnoBridge, bridgeName: {}", xBridgeName);
            } else {
                try {
                    Retry.retryCount(NoConnectException.class, RETRY_COUNT, 3 * 1000, new Retry.Command<Void>() {
                        @Override
                        public Void call() throws Throwable {
                            try {
                                XConnector xConnector = $c(xLocaleComponentContext, XConnector.class, CONNECTOR);
                                XConnection xConnection = xConnector.connect(xBridgeUrl);
                                /*-
                                 * 这里是通过名称来判断, 即使工厂不是同一个实例, 如果 bridge 名称相同, 依然会拿到同一 Bridge
                                 * 非并发会抛出 BridgeExistsException
                                 * createBridge 存在并发问题, 非并发情况下如果 bridge 存在会抛出 BridgeExistsException
                                 */
                                xBridge = xBridgeFactory.createBridge(xBridgeName, xBridgeType, xConnection, null);

                                if (null == getComponentContext()) {
                                    throw new IllegalStateException("no component context!");
                                }

                                LOG.info("UnoBridge connect successful, bridgeName:{}, UNO Url: {}", xBridgeName, xBridgeUrl);

                                // 添加组件监听, 处理桥断开事件
                                XComponent xBridgeComponent = $q(XComponent.class, xBridge);
                                xBridgeComponent.addEventListener(xBridgeMonitor);
                            } catch (NoConnectException ex) {
                                LOG.warn("UnoBridge connect failed, retry please, UNO Url: {}", xBridgeUrl);
                                throw ex;
                            }
                            return null;
                        }
                    });
                } catch (Throwable ex) {
                    if (ex instanceof NoConnectException) {
                        throw new RuntimeException(String.format("UnoBridge connect failed, UNO Url: %s", xBridgeUrl), ex);
                    }
                    throw new RuntimeException(ex);
                }
            }

            /*
            //
            XTextDocument text = $q(XTextDocument.class, createComponentLoader().loadComponentFromURL("private:factory/swriter", "_blank", 0, new PropertyValue[0]));
            // 获取文档引用
            XText xText = text.getText();
            // 获取文档属性
            XPropertySet xDocProps = $(XPropertySet.class, xText);
            // 建立光标
            XTextCursor xTextCursor = xText.createTextCursor();
            //
            XSentenceCursor xSentenceCursor = $(XSentenceCursor.class, xTextCursor);
            XWordCursor xWordCursor = $(XWordCursor.class, xTextCursor);
            //
            XPropertySet xCursorProps = $(XPropertySet.class, xTextCursor);
            xCursorProps.setPropertyValue("CharFontName", "宋体");
            xCursorProps.setPropertyValue("CharWeight", new Float(FontWeight.BOLD));
            xCursorProps.setPropertyValue("CharHeight", new Float(10.5));
            // 居中显示
            xCursorProps.setPropertyValue("ParaAdjust", com.sun.star.style.ParagraphAdjust.CENTER);
            xText.insertString(xSentenceCursor, "Hello World", true);
            */

            // $(XSpreadsheetDocument.class, createComponentLoader().loadComponentFromURL("private:factory/scalc", "_blank", 0, new PropertyValue[0]))
            // $(XExportFilter.class, createComponentLoader().loadComponentFromURL("private:factory/scalc", "_blank", 0, new PropertyValue[0]))
            // $(XPresentationPage.class, createComponentLoader().loadComponentFromURL("private:factory/simpress", "_blank", 0, new PropertyValue[0]));
            // XComponent xComponent = createComponentLoader().loadComponentFromURL("private:factory/simpress", "_blank", 0, new PropertyValue[0]);

            return this;
        }
    }

    /**
     * 获取当前 UNO Bridge 中的组件
     *
     * @param xCmpType UNO 组件类型
     * @param name     UNO 组件名称
     * @return 对应组件或null
     */
    public <T> T getComponent(Class<T> xCmpType, String name) {
        if (null == xBridge) {
            throw new IllegalStateException("UnoBridge is not connection, must be call connect method first");
        }
        return $q(xCmpType, xBridge.getInstance(name));
    }

    /**
     * 获取当前 UNO Bridge 的远程服务管理器(ServiceManager)
     */
    public XMultiComponentFactory getServiceManager() {
        /*-
         * 该方式为获取 "/singleton/com.sun.star.lang.theServiceManager" or "StartOffice.ServiceManager"
         * 命名单例对象的快捷操作
         */
        return getComponentContext().getServiceManager();
    }

    /**
     * 获取当前 UNO Bridge 所属的远程组件上下文
     */
    public XComponentContext getComponentContext() {
        return getComponent(XComponentContext.class, BRIDGE_COMP_CONTEXT);
    }

    /**
     * 使用当前 UNO Bridge 的远程组件上下文中创建一个 XComponentLoader
     *
     * @return XComponentLoader
     * @throws Exception
     */
    public XComponentLoader createComponentLoader() throws Exception {
        return $c(getComponentContext(), XComponentLoader.class, BRIDGE_DESKTOP);
    }


    /**
     * 当前 UNO Bridge 是否已经连接
     *
     * @return 是否已经连接
     */
    public boolean isConnected() {
        return xBridge != null;
    }

    /**
     * 断开当前 UNO Bridge 连接
     */
    public void disconnect() {
        if (!isConnected()) {
            return;
        }
        XBridge xBridge = this.xBridge;
        this.xBridge = null;

        LOG.info("UnoBridge disconnected, UNO Url: {}", getBridgeUrl());
        try {
            XComponent xBridgeComponent = $q(XComponent.class, xBridge);
            if (null != xBridgeComponent) {
                xBridgeComponent.removeEventListener(xBridgeMonitor);
                xBridgeComponent.dispose();
            }
        } catch (Exception ignore) {
            // ignore
            LOG.warn("dispose xBridge Component exception", ignore);
        }
    }

    /**
     * 获取当前 UNO Bridge 名称
     */
    public String getBridgeName() {
        return null != xBridge ? xBridge.getName() : null;
    }

    public String getBridgeUrl() {
        return "uno:" + unoUrl.getConnectionAndParametersAsString() + ";" + unoUrl.getProtocolAndParametersAsString() + ";" + unoUrl.getRootOid();
    }
}
